package com.roy.royminidao.common.framework.lang.db;

import java.util.List;
import java.util.Properties;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.MappedStatement.Builder;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * 分页拦截器<br>
 * 分页拦截器
 * 
 * @author rong
 * @see [相关类/方法]（可选）
 * @since [产品/模块版本] （可选）
 */
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
        RowBounds.class, ResultHandler.class }) })
public class PaginationInterceptor implements Interceptor {

    static int MAPPED_STATEMENT_INDEX = 0;

    static int PARAMETER_INDEX = 1;

    static int ROWBOUNDS_INDEX = 2;
    private Dialect dialect;
    /**
     * 日志工具
     */
    private static Logger logger = LoggerFactory.getLogger(PaginationInterceptor.class);

    /*
     * (non-Javadoc)
     * @see org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin .Invocation)
     */
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] queryArgs = invocation.getArgs();

        RowBounds rowBounds = (RowBounds) queryArgs[ROWBOUNDS_INDEX];
        int offset = rowBounds.getOffset();
        int limit = rowBounds.getLimit();

        // 非分页查询
        if (offset == RowBounds.NO_ROW_OFFSET && limit == RowBounds.NO_ROW_LIMIT) {
            return invocation.proceed();
        }

        // 分页查询
        MappedStatement ms = (MappedStatement) queryArgs[MAPPED_STATEMENT_INDEX];
        Object parameter = queryArgs[PARAMETER_INDEX];
        BoundSql boundSql = ms.getBoundSql(parameter);
        String sql = boundSql.getSql().trim();

        /*
         * 计算当前查询的总记录数，并根据分页条件抽取分页数据
         */
        if (offset == -1 && limit == -1) {
            StringBuffer pageCount = new StringBuffer(sql.length() + 100);
            pageCount.append("select count(*) as totalCount from (").append(sql).append(") as t");
            sql = pageCount.toString();
            queryArgs[ROWBOUNDS_INDEX] = new RowBounds();
        } else {
            if (offset >= RowBounds.NO_ROW_OFFSET && limit != RowBounds.NO_ROW_LIMIT) {
                sql = dialect.getLimitString(sql, offset, limit);
            }
        }

        BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, boundSql.getParameterMappings(),
                boundSql.getParameterObject());
        // 解决foreach的分页参数丢失的bug
        MappedStatement newMs = copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
        List<ParameterMapping> pmLs = boundSql.getParameterMappings();
        for (ParameterMapping pm : pmLs) {
            String propertyName = pm.getProperty();
            // 这里只是增加以"_frec_"开始的参数，表示是for each参数，其他参数可以从ParameterObject获取。
            if (propertyName.startsWith("__frch_")) {
                newBoundSql.setAdditionalParameter(propertyName, boundSql.getAdditionalParameter(propertyName));
            }
        }
        queryArgs[ROWBOUNDS_INDEX] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);
        queryArgs[MAPPED_STATEMENT_INDEX] = newMs;

        return invocation.proceed();
    }

    /**
     * 创建分页后新的MappedStatement
     * 
     * @param ms
     * @param newSqlSource
     * @return
     */
    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {

        Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource,
                ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        String[] in = ms.getKeyProperties();
        String str = "";
        if (in == null || in.length == 0) {
        } else {
            for (int i = 0; i < in.length; i++) {
                if (i < in.length - 1) {
                    str += in[i] + ",";
                } else {
                    str += in[i];
                }
            }
        }
        builder.keyProperty(str);
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.cache(ms.getCache());
        builder.resultSetType(ms.getResultSetType());
        MappedStatement newMs = builder.build();
        return newMs;
    }

    /*
     * (non-Javadoc)
     * @see org.apache.ibatis.plugin.Interceptor#plugin(java.lang.Object)
     */
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /*
     * (non-Javadoc)
     * @see org.apache.ibatis.plugin.Interceptor#setProperties(java.util.Properties)
     */
    public void setProperties(Properties properties) {
        String dialectClass = properties.getProperty("dialectClass");
        try {
            dialect = (Dialect) Class.forName(dialectClass).newInstance();
        } catch (Exception e) {
            logger.error("获取或者加载实例化Dialect时发生异常", e);
        }
    }

    public static class BoundSqlSqlSource implements SqlSource {
        BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }
}
